package system.track;

import java.util.LinkedList;
import system.train.Train;
import system.communication.ConcreteMonitor;

/**
 * This class provides the actual behavior specified by the
 * block interface.  This is a block for Generic use; it regulates
 * the behavior of the majority of blocks on the track.
 * 
 * @author David Kolb
 * @version Cycle 1 - complete
 */
public class GenericBlock implements Block
{
    private int id;
    private Switch ccwSwitch;
    private Switch cwSwitch;
    private Direction state;
    private int speed;
    private Train theTrain;
    private LinkedList<Sensor> ccwSensors;
    private LinkedList<Sensor> cwSensors;

    /**
     * Constructor for Objects of class GenericBlock.  This creates an
     * instance of the most common type of Block available on the Track.
     * A GenericBlock has Sensors in two directions, Switches on each end,
     * a directional "state" that can either be clockwise or counterclockwise,
     * an ID, and a speed (of the train currently upon it).
     */
    public GenericBlock()
    {
        ccwSensors = new LinkedList<Sensor>();
        cwSensors = new LinkedList<Sensor>();
        id = 0;
        state = Direction.CLOCKWISE;
        speed = 0;
    }

    /**
     * Registers an integer id with the block.
     * 
     * @param id an ID that corresponds to the blocks ID in the layout. 
     */
    public void setID(int id)
    {
        this.id = id;
    }

    /**
     * Retrieves the block's id.
     * 
     * @return an integer that corresponds to a block in the layout.
     */
    public int getID()
    {
        return id;
    }

    /**
     * Registers a switch as being located at the end of the 
     * block in the direction denoted by the argument d.
     *
     * @param d the direction denoting the end that the switch is on.
     * @param trackSwitch the switch located at the end of the block.
     */
    public void setSwitch(Direction d, Switch trackSwitch)
    {
        switch (d)
        {
            case CLOCKWISE:
                this.cwSwitch = trackSwitch;
                break;
            case COUNTERCLOCKWISE:
                this.ccwSwitch = trackSwitch;
                break;
            default:
                throw new IllegalArgumentException("Bad direction arg");
        }
    }

    /**
     * Returns the switch in the specified direction.
     * 
     * @param d the direction to get the switch from.
     * @return The switch to return, null if none.
     */
    public Switch getSwitch(Direction d)
    {
        Switch returnVal = null;
        switch (d)
        {
            case CLOCKWISE:
                returnVal = this.cwSwitch;
                break;
            case COUNTERCLOCKWISE:
                returnVal = this.ccwSwitch;
                break;
            default:
                throw new IllegalArgumentException("Bad direction arg.");
        }
        return returnVal;
    }

    /**
     * This function returns a boolean informing the caller if the upcoming
     * switch (based upon current direction) is able to be set.
     * 
     * @return true if there is a switch that can be set
     */
    public boolean canSetSwitch()
    {
        boolean returnVal = false;
        try
        {
            switch(this.state)
            {
                case CLOCKWISE:
                    if (this.cwSwitch.getEntrance() == this)
                    {
                        returnVal = true;
                    }
                    break;
                case COUNTERCLOCKWISE:
                    if (this.ccwSwitch.getEntrance() == this)
                    {
                        returnVal = true;
                    }
            }
        }
        catch (NullPointerException e)
        {
        }
        return returnVal;
    }

    /**
     * This sets the direction to one of the three directions defined
     * in the enum.
     * 
     * @see Direction
     * @param d one of the instantiated objects of the <code>Direction</code> enum.
     * @param speed the speed setting between 0 and 7
     * @throws IllegalArgumentException when speed < 0 || speed > 7
     */
    public void setDirection(Direction d, int speed)
    {
        if (speed > 7 || speed < 0)
        {
            throw new IllegalArgumentException("Bad speed argument: " + speed);
        }
        this.state = d;
        this.speed = speed;
        ConcreteMonitor.getInstance().setBlock(this.id, d, speed);
    }

    /**
     * This returns the current direction of a track.
     * 
     * @return one of the instantiated object of the <code>Direction</code> enum.
     */    
    public Direction getDirection()
    {
        return this.state;
    }

    /**
     * This returns a boolean based on whether this block is the special case
     * block that can park trains.
     * 
     * @return <code>true</code> if the block can park trains
     */
    public boolean isParkingBlock()
    {
        return false;
    }

    /**
     * In the case that the block can park a train, this instructs the
     * block as to whether or not it's parking a train.
     * 
     * @param flag <code>true</code> to park the train, <code>false</code> to 
     * allow the train to cross over.
     * @throws track.IllegalFunctionCallException when 
     * <code>isParkingBlock()</code> returns <code>false</code>
     * @see Block#isParkingBlock() 
     */
    public void setPark(boolean flag) throws IllegalFunctionCallException
    {
        throw new IllegalFunctionCallException("Not a parking block.");
    }

    /**
     * Places a train object on the block.  When a train travels from one 
     * block to the next, this is how the train informs the block that it is 
     * now running a train.
     * 
     * @param train the train object that is currently travelling on the block.
     */
    public void giveTrain(Train train)
    {
        this.theTrain = train;
    }

    /**
     * Returns whether there is a train on the block currently.
     * 
     * @return <code>true</code> if the train is on the block, <code>false</code>
     * if there is no train currently on the block.
     */
    public boolean hasTrain()
    {
        return (this.theTrain != null);
    }

    /**
     * Removes a train from a track block.  This will remove the train from
     * a block, returning the train to the caller.
     * 
     * @return the train that was on the block.
     */
    public Train takeTrain()
    {
        Train returnVal = this.theTrain;
        this.theTrain = null;
        return returnVal;
    }

    /**
     * Returns the set of sensors that can be tripped in
     * the specified direction.
     * 
     * @param d  the direction to look for sensors
     * @return a linked list of sensors
     */
    public LinkedList<Sensor> getSensors(Direction d)
    {
        LinkedList<Sensor> returnVal;
        switch(d)
        {
            case CLOCKWISE:
                returnVal = this.cwSensors;
                break;
            case COUNTERCLOCKWISE:
                returnVal = this.ccwSensors;
                break;
            default:
                throw new IllegalArgumentException("Bad direction arg.");
        }
        return returnVal;
    }

    /**
     * Adds a sensor to the set returned by getSensor().
     * 
     * @param d the direction to add the sensor to
     * @param s the sensor to add
     */
    public void addSensor(Direction d, Sensor s)
    {
        switch(d)
        {
            case CLOCKWISE:
                this.cwSensors.add(s);
                break;
            case COUNTERCLOCKWISE:
                this.ccwSensors.add(s);
                break;
            default:
                throw new IllegalArgumentException("Bad direction arg.");
        }
    }

    /**
     * Returns the next block that the train will encounter if it
     * continues to travel in its current Direction, depending upon the
     * angle that the next Switch is set to.
     * 
     * @param d the Direction the train is travelling
     * @return  the next Block the train will encounter
     */
    public Block getNextBlock(Direction d)
    {
        Block returnVal;
        Switch theSwitch;
        //find what switch the train will encounter
        switch (d)
        {
            case CLOCKWISE:
                theSwitch = this.cwSwitch;
                break;
            case COUNTERCLOCKWISE:
                theSwitch = this.ccwSwitch;
                break;
            default:
                throw new IllegalArgumentException("Bad direction arg.");

        }
        //Determine orientation.
        if (theSwitch.getEntrance() == this)
        { // We are at the entrance to a switch.
          // Grab the exit the switch is set to.
            returnVal = theSwitch.getExit(theSwitch.getAngle());
        }
        else
        { // We are at an exit to the switch, return the entrance.
            returnVal = theSwitch.getEntrance();
        }
        return returnVal;
    }
}
